package org.mozhu.mboot.core.config.support;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mozhu.mboot.core.config.ApplicationConfiguration;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ConfigBeanPostProcessor implements BeanPostProcessor, Ordered {

    protected static final Log log = LogFactory.getLog(ConfigBeanPostProcessor.class);

    private Map<String, List<BeanConfigurableProperty>> configurationLinkPoints = new HashMap<String, List<BeanConfigurableProperty>>();

    @Autowired
    private ApplicationConfiguration applicationConfiguration;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        Class<?> beanClass = bean.getClass();
        for (Method method : beanClass.getMethods()) {
            Value value = AnnotationUtils.findAnnotation(method, Value.class);
            if (value == null) {
                continue;
            }
            initBeanConfigurableProperty(trim(value.value()), bean, method);
        }

        for (Field field : beanClass.getDeclaredFields()) {
            Value value = AnnotationUtils.findAnnotation(field, Value.class);
            if (value == null) {
                continue;
            }
            String fieldName = field.getName();
            Class<?> fieldType = field.getType();
            String setterMethodName = "set" + StringUtils.capitalize(fieldName);
            Method method = ReflectionUtils.findMethod(beanClass, setterMethodName, fieldType);
            if (method == null) {
                continue;
            }
            initBeanConfigurableProperty(trim(value.value()), bean, method);
        }
        return bean;
    }

    private String trim(String value) {
        if (value.startsWith("${")) {
            value = value.substring("${".length());
        }
        if (value.endsWith("}")) {
            value = value.substring(0, value.length() - "}".length());
        }
        return value;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    private void initBeanConfigurableProperty(String configKey, Object bean, Method writeMethod) {
        List<BeanConfigurableProperty> properties = configurationLinkPoints.get(configKey);
        if (properties == null) {
            properties = new ArrayList<>();
            configurationLinkPoints.put(configKey, properties);
        }

        BeanConfigurableProperty beanConfigurableProperty = new BeanConfigurableProperty(configKey, bean, writeMethod);
        beanConfigurableProperty.setApplicationConfiguration(applicationConfiguration);
        properties.add(beanConfigurableProperty);
    }

    @Override
    public int getOrder() {
        // this is important, otherwise some bean can't get in postProcessBeforeInitialization
        // see PostProcessorRegistrationDelegate
        return Ordered.LOWEST_PRECEDENCE;
    }
}
